Home:ALL Converter>Constraint Crash after Dismissing View Controller

Constraint Crash after Dismissing View Controller

Ask Time:2018-11-10T10:05:27         Author:Davidhall

Json Formatter

I'm a bit new to Xcode and been trying to do things programatically. I have View Controller A, B, C, and D. I have a back button on C, and D. When going from D to C using self.dismiss it works fine, however when I go from C to B I am getting a crash that looks like it's a constraint issue and I have no idea why.

Again, the crash occurs when going from C to B. The error says no common ancestor for DropDownButton, but there is no DropDownButton on ViewController B, it exists on C the one I am trying to dismiss.

I would like to know more about how the view controllers dismissing and Auto Layout works, could someone point me in the right direction please?

"oneonone.DropDownButton:0x7fcfe9d30660'🇺🇸+1 ⌄'.bottom"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal. userInfo: (null)
2018-11-09 19:56:22.828322-0600 oneonone[62728:4835265] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor

UPDATE TO QUESTIONS:

Here is View Controller C, included is the var, adding it to subview, and how I dismiss this view controller

    lazy var countryCodes: DropDownButton = {
        let button = DropDownButton(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
        let us = flag(country: "US")
        let br = flag(country: "BR")
        let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
        button.backgroundColor = lightGray
        button.setTitle(us + "+1 \u{2304}", for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
        button.setTitleColor(UIColor.darkGray, for: .normal)
        button.uiView.dropDownOptions = [us + "+1", br + "+55", "+33", "+17", "+19"]
        return button
    }()

override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white

        [countryCodes].forEach{ view.addSubview($0) }

        setupLayout()

    }

func setupLayout(){
        countryCodes.translatesAutoresizingMaskIntoConstraints = false
        countryCodes.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 30).isActive = true
        countryCodes.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -77.5).isActive = true
        countryCodes.widthAnchor.constraint(equalToConstant: 85).isActive = true // guarantees this width for stack
        countryCodes.heightAnchor.constraint(equalToConstant: 40).isActive = true
}

@objc func buttonPressed(){
    self.dismiss(animated: true, completion: nil)
}

Here is the code in view controller B that (creates or presents?) View Controller C

@objc func phoneAuthButtonPressed(){
        let vc = phoneAuthViewController()
        self.present(vc, animated: true, completion: nil)
    }

UPDATE 2: ADDING THE CUSTOM CLASS

Here is the button code that I used as a custom class following a tutorial, I believe the problem lies in here

protocol dropDownProtocol {
    func dropDownPressed(string: String)
}

class DropDownButton: UIButton, dropDownProtocol {

    var uiView = DropDownView()

    var height = NSLayoutConstraint()

    var isOpen = false

    func dropDownPressed(string: String) {
        self.setTitle(string + " \u{2304}", for: .normal)
        self.titleLabel?.font = UIFont.systemFont(ofSize: 18)
        self.dismissDropDown()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.gray
        uiView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
        uiView.delegate = self
        uiView.layer.zPosition = 1 // show in front of other labels
        uiView.translatesAutoresizingMaskIntoConstraints = false
    }

    override func didMoveToSuperview() {
        self.superview?.addSubview(uiView)
        self.superview?.bringSubviewToFront(uiView)
        uiView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        uiView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        uiView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        height = uiView.heightAnchor.constraint(equalToConstant: 0)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // animates drop down list
        NSLayoutConstraint.deactivate([self.height])

        if self.uiView.tableView.contentSize.height > 150 {
            self.height.constant = 150
        } else {
            self.height.constant = self.uiView.tableView.contentSize.height
        }

        if isOpen == false {
            isOpen = true
            NSLayoutConstraint.activate([self.height])
            UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
                self.uiView.layoutIfNeeded()
                self.uiView.center.y += self.uiView.frame.height / 2
            }, completion: nil)
        } else {
            dismissDropDown()
        }
    }

    func dismissDropDown(){
        isOpen = false
        self.height.constant = 0
        NSLayoutConstraint.activate([self.height])
        UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
            self.uiView.center.y -= self.uiView.frame.height / 2
            self.uiView.layoutIfNeeded()
        }, completion: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


class DropDownView: UIView, UITableViewDelegate, UITableViewDataSource {

    var dropDownOptions = [String]()

    var tableView = UITableView()

    var delegate : dropDownProtocol!

    let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)

    override init(frame: CGRect) {
        super.init(frame: frame)
        tableView.backgroundColor = lightGray
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(tableView) // can not come after constraints
        tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dropDownOptions.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = dropDownOptions[indexPath.row]
        cell.textLabel?.font = UIFont.systemFont(ofSize: 14)
        cell.textLabel?.textColor = UIColor.darkGray
        cell.backgroundColor = lightGray
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.delegate.dropDownPressed(string: dropDownOptions[indexPath.row])
        self.tableView.deselectRow(at: indexPath, animated: true)
    }

}

Author:Davidhall,eproduced under the CC 4.0 BY-SA copyright license with a link to the original source and this disclaimer.
Link to original article:https://stackoverflow.com/questions/53235422/constraint-crash-after-dismissing-view-controller
Alin Lipan :

Replace your didMoveToSuperview function with this and it will work. This function also gets called when the view its removed from the superview and the superview will be nil and that's causing the crash.\n\n override func didMoveToSuperview() {\n if let superview = self.superview {\n self.superview?.addSubview(dropView)\n self.superview?.bringSubviewToFront(dropView)\n dropView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true\n dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true\n dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true\n height = dropView.heightAnchor.constraint(equalToConstant: 0)\n }\n}\n",
2019-08-24T14:44:41
kelin :

As I can see, the error says that one of countryCodes buttons is located in the different view than instructionLabel. They should have the same parent if you want them to be constrained by each other.",
2018-11-11T09:24:09
yy